This notebook explores the use of SingleR to perform cell-type annotation on datasets from the ScPCA project SCPCP000007 (Gawad lab data).

Note: The first time you run this code it may take a few more minutes due to reference downloads, but they will be cached for faster future execution.

0.1 Set Up

Use this code to obtain the PBMC reference from Zenodo: https://zenodo.org/record/4546839#.Y3PZ5oLMJhE

wget https://zenodo.org/record/4546839/files/ref.Rds
# load the R project
project_root <- here::here()
renv::load(project_root)
* Project '~/ALSF/scpca/sc-data-integration' loaded. [renv 0.15.5]
suppressPackageStartupMessages({
  library(SingleCellExperiment)
  library(SingleR)
  library(celldex)
  library(ggplot2)
})
theme_set(theme_bw())
set.seed(params$seed) # unclear if this is doing anything? probably not. but maybe later!

utils_dir <- file.path(project_root, "scripts", "utils")
source(file.path(utils_dir, "integration-helpers.R"))

sce_file_suffix <- "processed_citeseq.rds"

integrated_sce_file <- file.path(project_root, 
                                 params$integrated_sce_dir, 
                                 paste0(params$project_id, "_integrated_", params$integration_method, "_sce.rds")
                                 )
if (!file.exists(integrated_sce_file)){
  stop("Integrated SCE file could not be found.")
}

Read in the data:

library_metadata_df <- readr::read_tsv(file.path(project_root, params$library_file))
Rows: 25 Columns: 16── Column specification ───────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (15): project_name, submitter, library_biomaterial_id, sample_biomaterial_id, diagnosis, technology, se...
lgl  (1): has_CITEseq
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
integrated_sce <- readr::read_rds(integrated_sce_file)

# Define the unintegrated SCE filenames
sce_file_paths <- library_metadata_df %>%
  dplyr::filter(project_name == params$project_id) %>%
  dplyr::mutate(sce_file_path = file.path(
    project_root, 
    integration_input_dir, 
    sample_biomaterial_id, 
    glue::glue("{library_biomaterial_id}_{sce_file_suffix}")
  )) %>%
  dplyr::pull(sce_file_path)

0.2 SingleR annotation

Here we perform celltype annotation with the given reference in params$reference on each of the Gawad libraries and look at their UMAPs colored by celltype.

First, set the reference:

if (params$reference == "hpca") {
  ref_data <- celldex::HumanPrimaryCellAtlasData(ensembl = TRUE)
} else if (params$reference == "blueprint_encode") {
  ref_data <- celldex::BlueprintEncodeData(ensembl = TRUE)
} else {
  stop("Bad reference parameter; either 'hpca' or 'blueprint_encode'.")
}
snapshotDate(): 2022-04-26
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
snapshotDate(): 2022-04-25
loading from cache
require(“ensembldb”)
Warning: Unable to map 1470 of 19363 requested IDs.

Define some functions:

annotate_SingleR <- function(sce, ref_data, label_name = "label.main") {
  # label_name is either "label.main" or "label.fine" to label with reference
  #  broad or fine-grained celltypes, respectively
  
  #label_name <- "label.fine"
  # return updated sce and the predictions themselves
  preds <- SingleR::SingleR(
    test = sce, 
    ref = ref_data,
    labels = colData(ref_data)[,label_name], 
    BPPARAM = BiocParallel::MulticoreParam(4)
  )
  
  return(preds)
}

plot_SingleR_heatmap <- function(SingleR_annotations) {
  # SingleR_annotations: result object from running SingleR::SingleR
  # Make and print a heatmap 
  
  heatmap <- SingleR::plotScoreHeatmap(SingleR_annotations, 
                            # this is default but let's be explicit
                            show.pruned = FALSE)
  
  print(heatmap)
}

plot_SingleR_UMAP <- function(sce, colname, legend_ncol = 1) {
  # sce: SCE object containing a column `colname` with celltype predictions
  # colname: Name of column with celltypes
  
  # Ensure this is a factor 
  colData(sce)[,colname] <- as.factor(colData(sce)[,colname])
  
  umap_df <- tibble::as_tibble(reducedDim(sce, "UMAP")) %>%
    dplyr::select(UMAP1 = V1, UMAP2 = V2) %>%
    dplyr::mutate(celltypes = colData(sce)[,colname])
  
  plot_colors <- rainbow( length(levels(umap_df$celltypes)) )
  
  umap <- ggplot(umap_df) + 
    aes(x = UMAP1, y = UMAP2, color = celltypes) +
    geom_point(size = 0.2, alpha = 0.5) + 
    scale_color_manual(values = plot_colors) +
    guides(color = guide_legend(override.aes = list(size=3),
                                ncol = legend_ncol)) +
    ggtitle(glue::glue("Library UMAP with projected SingleR celltype annotations"))
  
  print(umap)
  
}

# Function to run and optionally plot SingleR
# Return SCE with annotations `SingleR_annotations` and `SingleR_annotations_collapsed` columns
run_SingleR <- function(sce, 
                        max_celltypes = params$max_celltypes, 
                        viz = TRUE) {
  # Print out the library
  print(unique( metadata(sce)$library ))
  
  # Run SingleR
  SingleR_results <- annotate_SingleR(sce, ref_data)
  
  # Add annotations into sce as a frequency-ordered factor
  sce$SingleR_annotations <- forcats::fct_infreq(SingleR_results$pruned.labels)
   
  # Create an additional column `SingleR_annotations_collapsed` with only max_celltypes
  # This will automatically create an "Other" group with the remaining non-top-max_celltypes celltypes
  sce$SingleR_annotations_collapsed <- forcats::fct_lump_n(
    sce$SingleR_annotations, 
    max_celltypes
  )
  
  # plot if TRUE
  if (viz) {
    
    # Plot heatmap
    plot_SingleR_heatmap(SingleR_results)
    
    # Plot two UMAPs: with all celltypes and with max_celltypes
    plot_SingleR_UMAP(sce, "SingleR_annotations", legend_ncol=2)
    plot_SingleR_UMAP(sce, "SingleR_annotations_collapsed")
  }
  
  # Return the updated SCE object
  return(sce)
}

Here we go!

# Read in all SCE files
sce_list <- purrr::map(
  sce_file_paths, 
  readr::read_rds
)
Warning: call dbDisconnect() when finished working with a connection
# Annotate them all, popping out some viz along the way if specified
sce_list_annotated <- purrr::map(sce_list, run_SingleR, viz = FALSE)
[1] "SCPCL000295"
[1] "SCPCL000296"
[1] "SCPCL000297"
[1] "SCPCL000298"
[1] "SCPCL000299"

0.3 UMAPs

This section has some UMAPs:

  • Celltype annotations from individual libraries applied to the integrated UMAP
  • ADT levels on the integrated UMAP for some proteins of interest: CD123 (leukemia marker) and CD3 (T-cell marker)
# First let's create a df of celltypes and ADTs:
extract_celltypes_adts <- function(sce) {
  # data frame of ADT counts
  adt_df <- logcounts(altExp(sce)) %>%
    as.matrix() %>%
    t() %>%
    tibble::as_tibble(rownames = "cell_barcode") %>%
    tidyr::pivot_longer(dplyr::starts_with("CD"),
                        names_to = "ADT", 
                        values_to = "logcounts")
  
  # Combine with data frame of annotations
  adt_df_anno <- tibble::tibble(
      celltype = sce$SingleR_annotations, 
      celltype_collapsed = sce$SingleR_annotations_collapsed,
      cell_barcode = rownames(colData(sce)),
      library = metadata(sce)$library
    ) %>%
    dplyr::inner_join(adt_df) %>%
    dplyr::mutate(cell_name = paste(cell_barcode, library, sep = "-"))
}

# This df is generally used below:
celltype_adt_df <- purrr::map_df(sce_list_annotated, extract_celltypes_adts) %>%
  # And join in UMAPs:
  dplyr::inner_join(
    reducedDim(integrated_sce, paste0(params$integration_method, "_UMAP")) %>%
      tibble::as_tibble(rownames = "cell_name") %>%
      dplyr::rename(UMAP1 = V1, UMAP2 = V2)
  ) %>%
  dplyr::select(-cell_barcode)
Joining, by = "cell_barcode"Joining, by = "cell_barcode"Joining, by = "cell_barcode"Joining, by = "cell_barcode"Joining, by = "cell_barcode"Warning: The `x` argument of `as_tibble.matrix()` must have unique column names if `.name_repair` is omitted as of tibble 2.0.0.
Using compatibility `.name_repair`.Joining, by = "cell_name"
celltype_adt_df
# RNA UMAP:
all_celltypes_rna_umap <- celltype_adt_df %>%
  dplyr::select(UMAP1, UMAP2, celltype) %>%
  dplyr::distinct() %>%
  ggplot() +
  aes(x = UMAP1, y = UMAP2, color = celltype) + 
  geom_point(size = 0.3, alpha = 0.4) + 
  guides(color = guide_legend(override.aes = list(size=3))) +
  ggtitle(glue::glue("Integrated UMAP with all SingleR-annotated celltypes shown"))

all_celltypes_rna_umap

all_celltypes_rna_umap +
  lims(
    x = c(-6, 4), 
    y = c(-5, 5.5)
  ) +
  ggtitle("The above UMAP, zoomed in")

# RNA UMAP:
max_celltypes_rna_umap <- celltype_adt_df %>%
  dplyr::select(UMAP1, UMAP2, celltype_collapsed) %>%
  dplyr::distinct() %>%
  ggplot() +
  aes(x = UMAP1, y = UMAP2, color = celltype_collapsed) + 
  geom_point(size = 0.3, alpha = 0.4) + 
  guides(color = guide_legend(override.aes = list(size=3))) +
  labs(
    title = glue::glue("Integrated UMAP with top {params$max_celltypes} SingleR-annotated celltypes shown"), 
    color = glue::glue("Top {params$max_celltypes} celltypes"))

max_celltypes_rna_umap

max_celltypes_rna_umap +
  lims(
    x = c(-6, 4), 
    y = c(-5, 5.5)
  ) +
  ggtitle("The above UMAP, zoomed in")

# function to make UMAPs colored by ADT
make_adt_umap <- function(celltype_adt_df, adt_name) {

  celltype_adt_df %>%
    dplyr::filter(ADT == adt_name) %>%
    ggplot() + 
    aes(x = UMAP1, y = UMAP2, color = logcounts) + 
    geom_point(size = 0.3, alpha = 0.4) +
    scale_color_viridis_c() + 
    labs(
      title = glue::glue("{adt_name} expression"),
      color = "Normalized expression")
}

make_adt_umap(celltype_adt_df, "CD3") # bottom left corner tiny dots!

make_adt_umap(celltype_adt_df, "CD123") # everything is tumor

make_adt_umap(celltype_adt_df, "CD45") # should be on all differentiated cells more or less\

make_adt_umap(celltype_adt_df, "CD19") # b cells

0.4 ADT distributions

This section explores ADT counts across predicted celltypes.


adt_celltype_violin <- function(df, celltype_column, adt_name) {
  
  # colors, using dplyr since tidyeval land
  fill_vector <- df %>%
    dplyr::pull({{celltype_column}}) %>%
    unique() %>%
    length() %>%
    rainbow()

  celltype_adt_df %>%
    dplyr::filter(ADT == adt_name) %>%
    dplyr::mutate(celltype_plot_column = forcats::fct_reorder({{celltype_column}}, logcounts)) %>%
    ggplot() + 
    aes(x = celltype_plot_column,
        y = logcounts, 
        fill = celltype_plot_column) + 
    geom_violin(alpha = 0.7) + 
    stat_summary() + 
    scale_fill_manual(values = fill_vector, 
                      name = "Celltypes") +
    ggtitle(glue::glue("{adt_name} normalized expression across celltypes"))

}

## CD19
adt_celltype_violin(celltype_adt_df, celltype, "CD19") + 
  theme(axis.text.x = element_text(angle = 30, hjust = 1))

adt_celltype_violin(celltype_adt_df, celltype_collapsed, "CD19")


## CD123
adt_celltype_violin(celltype_adt_df, celltype, "CD123") + 
  theme(axis.text.x = element_text(angle = 30, hjust = 1))

adt_celltype_violin(celltype_adt_df, celltype_collapsed, "CD123")

0.5 Comparing references

To build a sense of how different references perform, let’s just look at one SCE (arbitrarily chosen as the first):

sce <- sce_list[[1]]

Functions for comparing references:

# Return a tibble of predictions from the two references

# Define outside of function to avoid rerunning over and over and over AND OVER.
hpca_ref <- celldex::HumanPrimaryCellAtlasData(ensembl = TRUE)
snapshotDate(): 2022-04-26
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
snapshotDate(): 2022-04-25
loading from cache
Warning: Unable to map 1470 of 19363 requested IDs.
blueprint_encode_ref <- celldex::BlueprintEncodeData(ensembl = TRUE)
snapshotDate(): 2022-04-26
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
snapshotDate(): 2022-04-25
loading from cache
Warning: Unable to map 532 of 19859 requested IDs.
compare_predictions <- function(sce, 
                                label_name) {
  
  # HPCA prediction
  preds_hpca <- SingleR::SingleR(
    test = sce, 
    ref = hpca_ref,
    labels = colData(hpca_ref)[,label_name], 
    BPPARAM = BiocParallel::MulticoreParam(4)
  )
  
  # Blueprint/ENCODE prediction
  preds_be <- SingleR::SingleR(
    test = sce, 
    ref = blueprint_encode_ref,
    labels = colData(blueprint_encode_ref)[,label_name], 
    BPPARAM = BiocParallel::MulticoreParam(4)
  )
  
  # Combine results into a wide tibble:
  preds_df <- tibble::tibble(
    hpca = preds_hpca$labels, 
    cell_barcode = rownames(preds_hpca)) %>%
    dplyr::inner_join(
      tibble::tibble(
        blueprint_encode = preds_be$labels, 
        cell_barcode = rownames(preds_be)) 
    )

  return(preds_df)
}

The celldex package contains bulk RNA-Seq datasets for use as reference. These two are likely most useful:

The HPCA reference consists of publicly available microarray datasets derived from human primary cells (Mabbott et al. 2013). Most of the labels refer to blood subpopulations but cell types from other tissues are also available.

The Blueprint/ENCODE reference consists of bulk RNA-seq data for pure stroma and immune cells generated by Blueprint (Martens and Stunnenberg 2013) and ENCODE projects (The ENCODE Project Consortium 2012).

main_labels_df <- compare_predictions(sce, "label.main")
Joining, by = "cell_barcode"
fine_labels_df <- compare_predictions(sce, "label.fine")
Joining, by = "cell_barcode"

How do these predictions compare? Note that these references often use different labels for the same cell type so we shouldn’t expect a perfect diagonal below:

#function for making comparison heatmaps
make_comparison_heatmap <- function(df) {
  df %>%
    dplyr::mutate(
      hpca = forcats::fct_rev(forcats::fct_infreq(hpca)),
      blueprint_encode = forcats::fct_rev(forcats::fct_infreq(blueprint_encode))
    ) %>%
    dplyr::count(hpca, blueprint_encode) %>%
  ggplot() + 
  aes(x = blueprint_encode, y = hpca, fill = n) + 
  geom_tile(color = "black") + 
  geom_text(aes(label = n)) + 
  scale_fill_distiller(palette = "RdYlBu", name = "Num. cells") + 
  # background grid from theme_bw does not match up with rectangles.
  theme_classic() +
  theme(axis.text.x = element_text(angle = 30, hjust = 1))
}
make_comparison_heatmap(main_labels_df) + ggtitle("Broad label comparison")

make_comparison_heatmap(fine_labels_df) + ggtitle("Fine label comparison")

0.6 Session Info

sessionInfo()
R version 4.2.1 (2022-06-23)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Monterey 12.6.1

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats4    stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] ensembldb_2.20.2            AnnotationFilter_1.20.0     GenomicFeatures_1.48.3     
 [4] AnnotationDbi_1.58.0        magrittr_2.0.3              ggplot2_3.3.6              
 [7] celldex_1.6.0               SingleR_1.10.0              SingleCellExperiment_1.18.0
[10] SummarizedExperiment_1.26.1 Biobase_2.56.0              GenomicRanges_1.48.0       
[13] GenomeInfoDb_1.32.3         IRanges_2.30.0              S4Vectors_0.34.0           
[16] BiocGenerics_0.42.0         MatrixGenerics_1.8.1        matrixStats_0.62.0         

loaded via a namespace (and not attached):
  [1] colorspace_2.0-3              rjson_0.2.21                  ellipsis_0.3.2               
  [4] modeltools_0.2-23             rprojroot_2.0.3               XVector_0.36.0               
  [7] BiocNeighbors_1.14.0          rstudioapi_0.13               farver_2.1.1                 
 [10] flexmix_2.3-18                bit64_4.0.5                   interactiveDisplayBase_1.34.0
 [13] fansi_1.0.3                   xml2_1.3.3                    codetools_0.2-18             
 [16] splines_4.2.1                 sparseMatrixStats_1.8.0       cachem_1.0.6                 
 [19] knitr_1.39                    Rsamtools_2.12.0              dbplyr_2.2.1                 
 [22] png_0.1-7                     shiny_1.7.2                   BiocManager_1.30.18          
 [25] readr_2.1.2                   compiler_4.2.1                httr_1.4.3                   
 [28] lazyeval_0.2.2                assertthat_0.2.1              Matrix_1.4-1                 
 [31] fastmap_1.1.0                 cli_3.3.0                     later_1.3.0                  
 [34] BiocSingular_1.12.0           miQC_1.4.0                    htmltools_0.5.3              
 [37] prettyunits_1.1.1             tools_4.2.1                   rsvd_1.0.5                   
 [40] gtable_0.3.0                  glue_1.6.2                    GenomeInfoDbData_1.2.8       
 [43] dplyr_1.0.9                   rappdirs_0.3.3                Rcpp_1.0.9                   
 [46] vctrs_0.4.1                   Biostrings_2.64.0             rtracklayer_1.56.1           
 [49] ExperimentHub_2.4.0           DelayedMatrixStats_1.18.0     xfun_0.32                    
 [52] stringr_1.4.0                 beachmat_2.12.0               mime_0.12                    
 [55] lifecycle_1.0.1               irlba_2.3.5                   restfulr_0.0.15              
 [58] renv_0.15.5                   XML_3.99-0.10                 AnnotationHub_3.4.0          
 [61] zlibbioc_1.42.0               scales_1.2.0                  vroom_1.5.7                  
 [64] ProtGenerics_1.28.0           hms_1.1.1                     promises_1.2.0.1             
 [67] parallel_4.2.1                RColorBrewer_1.1-3            yaml_2.3.5                   
 [70] curl_4.3.2                    memoise_2.0.1                 biomaRt_2.52.0               
 [73] stringi_1.7.8                 RSQLite_2.2.15                BiocVersion_3.15.2           
 [76] BiocIO_1.6.0                  ScaledMatrix_1.4.0            filelock_1.0.2               
 [79] BiocParallel_1.30.3           rlang_1.0.4                   pkgconfig_2.0.3              
 [82] bitops_1.0-7                  lattice_0.20-45               purrr_0.3.4                  
 [85] labeling_0.4.2                GenomicAlignments_1.32.1      bit_4.0.4                    
 [88] tidyselect_1.1.2              here_1.0.1                    R6_2.5.1                     
 [91] generics_0.1.3                DelayedArray_0.22.0           DBI_1.1.3                    
 [94] pillar_1.8.0                  withr_2.5.0                   KEGGREST_1.36.3              
 [97] RCurl_1.98-1.8                nnet_7.3-17                   tibble_3.1.8                 
[100] crayon_1.5.1                  utf8_1.2.2                    BiocFileCache_2.4.0          
[103] tzdb_0.3.0                    progress_1.2.2                grid_4.2.1                   
[106] blob_1.2.3                    forcats_0.5.2                 digest_0.6.29                
[109] xtable_1.8-4                  tidyr_1.2.0                   httpuv_1.6.5                 
[112] munsell_0.5.0                 viridisLite_0.4.0            
LS0tCnBhcmFtczoKICBzZWVkOiAyMDIyCiAgbGlicmFyeV9maWxlOiAhciBmaWxlLnBhdGgoInNhbXBsZS1pbmZvIiwgInNjcGNhLXByb2Nlc3NlZC1saWJyYXJpZXMudHN2IikKICBwcm9qZWN0X2lkOiAiU0NQQ1AwMDAwMDciCiAgcmVmZXJlbmNlOiAiaHBjYSIKICBpbnRlZ3JhdGVkX3NjZV9kaXI6ICFyIGZpbGUucGF0aCgicmVzdWx0cyIsICJzY3BjYSIsICJpbnRlZ3JhdGVkX3NjZSIpCiAgaW50ZWdyYXRpb25fbWV0aG9kOiAiZmFzdG1ubiIKICBtYXhfY2VsbHR5cGVzOiA1CnRpdGxlOiAiQ2VsbCB0eXBlIGFubm90YXRpb24gZXhwbG9yYXRpb24iCmF1dGhvcjogIkRhdGEgTGFiIgpkYXRlOiAiYHIgcGFyYW1zJGRhdGVgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQotLS0KClRoaXMgbm90ZWJvb2sgZXhwbG9yZXMgdGhlIHVzZSBvZiBbYFNpbmdsZVJgXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvcmVsZWFzZS9TaW5nbGVSQm9vay8pIHRvIHBlcmZvcm0gY2VsbC10eXBlIGFubm90YXRpb24gb24gZGF0YXNldHMgZnJvbSB0aGUgU2NQQ0EgcHJvamVjdCBTQ1BDUDAwMDAwNyAoR2F3YWQgbGFiIGRhdGEpLgoKKipOb3RlOioqIFRoZSBmaXJzdCB0aW1lIHlvdSBydW4gdGhpcyBjb2RlIGl0IG1heSB0YWtlIGEgZmV3IG1vcmUgbWludXRlcyBkdWUgdG8gcmVmZXJlbmNlIGRvd25sb2FkcywgYnV0IHRoZXkgd2lsbCBiZSBjYWNoZWQgZm9yIGZhc3RlciBmdXR1cmUgZXhlY3V0aW9uLgoKIyMgU2V0IFVwCgpVc2UgdGhpcyBjb2RlIHRvIG9idGFpbiB0aGUgUEJNQyByZWZlcmVuY2UgZnJvbSBaZW5vZG86IGh0dHBzOi8vemVub2RvLm9yZy9yZWNvcmQvNDU0NjgzOSMuWTNQWjVvTE1KaEUKYGBge3NoLCBldmFsPUZBTFNFfQp3Z2V0IGh0dHBzOi8vemVub2RvLm9yZy9yZWNvcmQvNDU0NjgzOS9maWxlcy9yZWYuUmRzCmBgYAoKCmBgYHtyIHNldHVwfQojIGxvYWQgdGhlIFIgcHJvamVjdApwcm9qZWN0X3Jvb3QgPC0gaGVyZTo6aGVyZSgpCnJlbnY6OmxvYWQocHJvamVjdF9yb290KQoKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQogIGxpYnJhcnkoU2luZ2xlUikKICBsaWJyYXJ5KGNlbGxkZXgpCiAgbGlicmFyeShnZ3Bsb3QyKQp9KQp0aGVtZV9zZXQodGhlbWVfYncoKSkKc2V0LnNlZWQocGFyYW1zJHNlZWQpICMgdW5jbGVhciBpZiB0aGlzIGlzIGRvaW5nIGFueXRoaW5nPyBwcm9iYWJseSBub3QuIGJ1dCBtYXliZSBsYXRlciEKCnV0aWxzX2RpciA8LSBmaWxlLnBhdGgocHJvamVjdF9yb290LCAic2NyaXB0cyIsICJ1dGlscyIpCnNvdXJjZShmaWxlLnBhdGgodXRpbHNfZGlyLCAiaW50ZWdyYXRpb24taGVscGVycy5SIikpCgpzY2VfZmlsZV9zdWZmaXggPC0gInByb2Nlc3NlZF9jaXRlc2VxLnJkcyIKCmludGVncmF0ZWRfc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKHByb2plY3Rfcm9vdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtcyRpbnRlZ3JhdGVkX3NjZV9kaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAocGFyYW1zJHByb2plY3RfaWQsICJfaW50ZWdyYXRlZF8iLCBwYXJhbXMkaW50ZWdyYXRpb25fbWV0aG9kLCAiX3NjZS5yZHMiKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCmlmICghZmlsZS5leGlzdHMoaW50ZWdyYXRlZF9zY2VfZmlsZSkpewogIHN0b3AoIkludGVncmF0ZWQgU0NFIGZpbGUgY291bGQgbm90IGJlIGZvdW5kLiIpCn0KYGBgCgpSZWFkIGluIHRoZSBkYXRhOgoKYGBge3J9CmxpYnJhcnlfbWV0YWRhdGFfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KGZpbGUucGF0aChwcm9qZWN0X3Jvb3QsIHBhcmFtcyRsaWJyYXJ5X2ZpbGUpKQoKaW50ZWdyYXRlZF9zY2UgPC0gcmVhZHI6OnJlYWRfcmRzKGludGVncmF0ZWRfc2NlX2ZpbGUpCgojIERlZmluZSB0aGUgdW5pbnRlZ3JhdGVkIFNDRSBmaWxlbmFtZXMKc2NlX2ZpbGVfcGF0aHMgPC0gbGlicmFyeV9tZXRhZGF0YV9kZiAlPiUKICBkcGx5cjo6ZmlsdGVyKHByb2plY3RfbmFtZSA9PSBwYXJhbXMkcHJvamVjdF9pZCkgJT4lCiAgZHBseXI6Om11dGF0ZShzY2VfZmlsZV9wYXRoID0gZmlsZS5wYXRoKAogICAgcHJvamVjdF9yb290LCAKICAgIGludGVncmF0aW9uX2lucHV0X2RpciwgCiAgICBzYW1wbGVfYmlvbWF0ZXJpYWxfaWQsIAogICAgZ2x1ZTo6Z2x1ZSgie2xpYnJhcnlfYmlvbWF0ZXJpYWxfaWR9X3tzY2VfZmlsZV9zdWZmaXh9IikKICApKSAlPiUKICBkcGx5cjo6cHVsbChzY2VfZmlsZV9wYXRoKQpgYGAKCgojIyBgU2luZ2xlUmAgYW5ub3RhdGlvbgoKSGVyZSB3ZSBwZXJmb3JtIGNlbGx0eXBlIGFubm90YXRpb24gd2l0aCB0aGUgZ2l2ZW4gcmVmZXJlbmNlIGluIGBwYXJhbXMkcmVmZXJlbmNlYCBvbiBlYWNoIG9mIHRoZSBHYXdhZCBsaWJyYXJpZXMgYW5kIGxvb2sgYXQgdGhlaXIgVU1BUHMgY29sb3JlZCBieSBjZWxsdHlwZS4KCkZpcnN0LCBzZXQgdGhlIHJlZmVyZW5jZToKYGBge3J9CmlmIChwYXJhbXMkcmVmZXJlbmNlID09ICJocGNhIikgewogIHJlZl9kYXRhIDwtIGNlbGxkZXg6Okh1bWFuUHJpbWFyeUNlbGxBdGxhc0RhdGEoZW5zZW1ibCA9IFRSVUUpCn0gZWxzZSBpZiAocGFyYW1zJHJlZmVyZW5jZSA9PSAiYmx1ZXByaW50X2VuY29kZSIpIHsKICByZWZfZGF0YSA8LSBjZWxsZGV4OjpCbHVlcHJpbnRFbmNvZGVEYXRhKGVuc2VtYmwgPSBUUlVFKQp9IGVsc2UgewogIHN0b3AoIkJhZCByZWZlcmVuY2UgcGFyYW1ldGVyOyBlaXRoZXIgJ2hwY2EnIG9yICdibHVlcHJpbnRfZW5jb2RlJy4iKQp9CmBgYAoKRGVmaW5lIHNvbWUgZnVuY3Rpb25zOgpgYGB7cn0KYW5ub3RhdGVfU2luZ2xlUiA8LSBmdW5jdGlvbihzY2UsIHJlZl9kYXRhLCBsYWJlbF9uYW1lID0gImxhYmVsLm1haW4iKSB7CiAgIyBsYWJlbF9uYW1lIGlzIGVpdGhlciAibGFiZWwubWFpbiIgb3IgImxhYmVsLmZpbmUiIHRvIGxhYmVsIHdpdGggcmVmZXJlbmNlCiAgIyAgYnJvYWQgb3IgZmluZS1ncmFpbmVkIGNlbGx0eXBlcywgcmVzcGVjdGl2ZWx5CiAgCiAgI2xhYmVsX25hbWUgPC0gImxhYmVsLmZpbmUiCiAgIyByZXR1cm4gdXBkYXRlZCBzY2UgYW5kIHRoZSBwcmVkaWN0aW9ucyB0aGVtc2VsdmVzCiAgcHJlZHMgPC0gU2luZ2xlUjo6U2luZ2xlUigKICAgIHRlc3QgPSBzY2UsIAogICAgcmVmID0gcmVmX2RhdGEsCiAgICBsYWJlbHMgPSBjb2xEYXRhKHJlZl9kYXRhKVssbGFiZWxfbmFtZV0sIAogICAgQlBQQVJBTSA9IEJpb2NQYXJhbGxlbDo6TXVsdGljb3JlUGFyYW0oNCkKICApCiAgCiAgcmV0dXJuKHByZWRzKQp9CgpwbG90X1NpbmdsZVJfaGVhdG1hcCA8LSBmdW5jdGlvbihTaW5nbGVSX2Fubm90YXRpb25zKSB7CiAgIyBTaW5nbGVSX2Fubm90YXRpb25zOiByZXN1bHQgb2JqZWN0IGZyb20gcnVubmluZyBTaW5nbGVSOjpTaW5nbGVSCiAgIyBNYWtlIGFuZCBwcmludCBhIGhlYXRtYXAgCiAgCiAgaGVhdG1hcCA8LSBTaW5nbGVSOjpwbG90U2NvcmVIZWF0bWFwKFNpbmdsZVJfYW5ub3RhdGlvbnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0aGlzIGlzIGRlZmF1bHQgYnV0IGxldCdzIGJlIGV4cGxpY2l0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93LnBydW5lZCA9IEZBTFNFKQogIAogIHByaW50KGhlYXRtYXApCn0KCnBsb3RfU2luZ2xlUl9VTUFQIDwtIGZ1bmN0aW9uKHNjZSwgY29sbmFtZSwgbGVnZW5kX25jb2wgPSAxKSB7CiAgIyBzY2U6IFNDRSBvYmplY3QgY29udGFpbmluZyBhIGNvbHVtbiBgY29sbmFtZWAgd2l0aCBjZWxsdHlwZSBwcmVkaWN0aW9ucwogICMgY29sbmFtZTogTmFtZSBvZiBjb2x1bW4gd2l0aCBjZWxsdHlwZXMKICAKICAjIEVuc3VyZSB0aGlzIGlzIGEgZmFjdG9yIAogIGNvbERhdGEoc2NlKVssY29sbmFtZV0gPC0gYXMuZmFjdG9yKGNvbERhdGEoc2NlKVssY29sbmFtZV0pCiAgCiAgdW1hcF9kZiA8LSB0aWJibGU6OmFzX3RpYmJsZShyZWR1Y2VkRGltKHNjZSwgIlVNQVAiKSkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFVNQVAxID0gVjEsIFVNQVAyID0gVjIpICU+JQogICAgZHBseXI6Om11dGF0ZShjZWxsdHlwZXMgPSBjb2xEYXRhKHNjZSlbLGNvbG5hbWVdKQogIAogIHBsb3RfY29sb3JzIDwtIHJhaW5ib3coIGxlbmd0aChsZXZlbHModW1hcF9kZiRjZWxsdHlwZXMpKSApCiAgCiAgdW1hcCA8LSBnZ3Bsb3QodW1hcF9kZikgKyAKICAgIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjZWxsdHlwZXMpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IDAuMiwgYWxwaGEgPSAwLjUpICsgCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGxvdF9jb2xvcnMpICsKICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IGxlZ2VuZF9uY29sKSkgKwogICAgZ2d0aXRsZShnbHVlOjpnbHVlKCJMaWJyYXJ5IFVNQVAgd2l0aCBwcm9qZWN0ZWQgU2luZ2xlUiBjZWxsdHlwZSBhbm5vdGF0aW9ucyIpKQogIAogIHByaW50KHVtYXApCiAgCn0KCiMgRnVuY3Rpb24gdG8gcnVuIGFuZCBvcHRpb25hbGx5IHBsb3QgU2luZ2xlUgojIFJldHVybiBTQ0Ugd2l0aCBhbm5vdGF0aW9ucyBgU2luZ2xlUl9hbm5vdGF0aW9uc2AgYW5kIGBTaW5nbGVSX2Fubm90YXRpb25zX2NvbGxhcHNlZGAgY29sdW1ucwpydW5fU2luZ2xlUiA8LSBmdW5jdGlvbihzY2UsIAogICAgICAgICAgICAgICAgICAgICAgICBtYXhfY2VsbHR5cGVzID0gcGFyYW1zJG1heF9jZWxsdHlwZXMsIAogICAgICAgICAgICAgICAgICAgICAgICB2aXogPSBUUlVFKSB7CiAgIyBQcmludCBvdXQgdGhlIGxpYnJhcnkKICBwcmludCh1bmlxdWUoIG1ldGFkYXRhKHNjZSkkbGlicmFyeSApKQogIAogICMgUnVuIFNpbmdsZVIKICBTaW5nbGVSX3Jlc3VsdHMgPC0gYW5ub3RhdGVfU2luZ2xlUihzY2UsIHJlZl9kYXRhKQogIAogICMgQWRkIGFubm90YXRpb25zIGludG8gc2NlIGFzIGEgZnJlcXVlbmN5LW9yZGVyZWQgZmFjdG9yCiAgc2NlJFNpbmdsZVJfYW5ub3RhdGlvbnMgPC0gZm9yY2F0czo6ZmN0X2luZnJlcShTaW5nbGVSX3Jlc3VsdHMkcHJ1bmVkLmxhYmVscykKICAgCiAgIyBDcmVhdGUgYW4gYWRkaXRpb25hbCBjb2x1bW4gYFNpbmdsZVJfYW5ub3RhdGlvbnNfY29sbGFwc2VkYCB3aXRoIG9ubHkgbWF4X2NlbGx0eXBlcwogICMgVGhpcyB3aWxsIGF1dG9tYXRpY2FsbHkgY3JlYXRlIGFuICJPdGhlciIgZ3JvdXAgd2l0aCB0aGUgcmVtYWluaW5nIG5vbi10b3AtbWF4X2NlbGx0eXBlcyBjZWxsdHlwZXMKICBzY2UkU2luZ2xlUl9hbm5vdGF0aW9uc19jb2xsYXBzZWQgPC0gZm9yY2F0czo6ZmN0X2x1bXBfbigKICAgIHNjZSRTaW5nbGVSX2Fubm90YXRpb25zLCAKICAgIG1heF9jZWxsdHlwZXMKICApCiAgCiAgIyBwbG90IGlmIFRSVUUKICBpZiAodml6KSB7CiAgICAKICAgICMgUGxvdCBoZWF0bWFwCiAgICBwbG90X1NpbmdsZVJfaGVhdG1hcChTaW5nbGVSX3Jlc3VsdHMpCiAgICAKICAgICMgUGxvdCB0d28gVU1BUHM6IHdpdGggYWxsIGNlbGx0eXBlcyBhbmQgd2l0aCBtYXhfY2VsbHR5cGVzCiAgICBwbG90X1NpbmdsZVJfVU1BUChzY2UsICJTaW5nbGVSX2Fubm90YXRpb25zIiwgbGVnZW5kX25jb2w9MikKICAgIHBsb3RfU2luZ2xlUl9VTUFQKHNjZSwgIlNpbmdsZVJfYW5ub3RhdGlvbnNfY29sbGFwc2VkIikKICB9CiAgCiAgIyBSZXR1cm4gdGhlIHVwZGF0ZWQgU0NFIG9iamVjdAogIHJldHVybihzY2UpCn0KYGBgCgpIZXJlIHdlIGdvIQoKYGBge3J9CiMgUmVhZCBpbiBhbGwgU0NFIGZpbGVzCnNjZV9saXN0IDwtIHB1cnJyOjptYXAoCiAgc2NlX2ZpbGVfcGF0aHMsIAogIHJlYWRyOjpyZWFkX3JkcwopCgojIEFubm90YXRlIHRoZW0gYWxsLCBwb3BwaW5nIG91dCBzb21lIHZpeiBhbG9uZyB0aGUgd2F5IGlmIHNwZWNpZmllZApzY2VfbGlzdF9hbm5vdGF0ZWQgPC0gcHVycnI6Om1hcChzY2VfbGlzdCwgcnVuX1NpbmdsZVIsIHZpeiA9IEZBTFNFKQpgYGAKCiMjIFVNQVBzCgpUaGlzIHNlY3Rpb24gaGFzIHNvbWUgVU1BUHM6CgotIENlbGx0eXBlIGFubm90YXRpb25zIGZyb20gaW5kaXZpZHVhbCBsaWJyYXJpZXMgYXBwbGllZCB0byB0aGUgaW50ZWdyYXRlZCBVTUFQCi0gQURUIGxldmVscyBvbiB0aGUgaW50ZWdyYXRlZCBVTUFQIGZvciBzb21lIHByb3RlaW5zIG9mIGludGVyZXN0OiBgQ0QxMjNgIChsZXVrZW1pYSBtYXJrZXIpIGFuZCBgQ0QzYCAoVC1jZWxsIG1hcmtlcikKCgpgYGB7cn0KIyBGaXJzdCBsZXQncyBjcmVhdGUgYSBkZiBvZiBjZWxsdHlwZXMgYW5kIEFEVHM6CmV4dHJhY3RfY2VsbHR5cGVzX2FkdHMgPC0gZnVuY3Rpb24oc2NlKSB7CiAgIyBkYXRhIGZyYW1lIG9mIEFEVCBjb3VudHMKICBhZHRfZGYgPC0gbG9nY291bnRzKGFsdEV4cChzY2UpKSAlPiUKICAgIGFzLm1hdHJpeCgpICU+JQogICAgdCgpICU+JQogICAgdGliYmxlOjphc190aWJibGUocm93bmFtZXMgPSAiY2VsbF9iYXJjb2RlIikgJT4lCiAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKGRwbHlyOjpzdGFydHNfd2l0aCgiQ0QiKSwKICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiQURUIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJsb2djb3VudHMiKQogIAogICMgQ29tYmluZSB3aXRoIGRhdGEgZnJhbWUgb2YgYW5ub3RhdGlvbnMKICBhZHRfZGZfYW5ubyA8LSB0aWJibGU6OnRpYmJsZSgKICAgICAgY2VsbHR5cGUgPSBzY2UkU2luZ2xlUl9hbm5vdGF0aW9ucywgCiAgICAgIGNlbGx0eXBlX2NvbGxhcHNlZCA9IHNjZSRTaW5nbGVSX2Fubm90YXRpb25zX2NvbGxhcHNlZCwKICAgICAgY2VsbF9iYXJjb2RlID0gcm93bmFtZXMoY29sRGF0YShzY2UpKSwKICAgICAgbGlicmFyeSA9IG1ldGFkYXRhKHNjZSkkbGlicmFyeQogICAgKSAlPiUKICAgIGRwbHlyOjppbm5lcl9qb2luKGFkdF9kZikgJT4lCiAgICBkcGx5cjo6bXV0YXRlKGNlbGxfbmFtZSA9IHBhc3RlKGNlbGxfYmFyY29kZSwgbGlicmFyeSwgc2VwID0gIi0iKSkKfQoKIyBUaGlzIGRmIGlzIGdlbmVyYWxseSB1c2VkIGJlbG93OgpjZWxsdHlwZV9hZHRfZGYgPC0gcHVycnI6Om1hcF9kZihzY2VfbGlzdF9hbm5vdGF0ZWQsIGV4dHJhY3RfY2VsbHR5cGVzX2FkdHMpICU+JQogICMgQW5kIGpvaW4gaW4gVU1BUHM6CiAgZHBseXI6OmlubmVyX2pvaW4oCiAgICByZWR1Y2VkRGltKGludGVncmF0ZWRfc2NlLCBwYXN0ZTAocGFyYW1zJGludGVncmF0aW9uX21ldGhvZCwgIl9VTUFQIikpICU+JQogICAgICB0aWJibGU6OmFzX3RpYmJsZShyb3duYW1lcyA9ICJjZWxsX25hbWUiKSAlPiUKICAgICAgZHBseXI6OnJlbmFtZShVTUFQMSA9IFYxLCBVTUFQMiA9IFYyKQogICkgJT4lCiAgZHBseXI6OnNlbGVjdCgtY2VsbF9iYXJjb2RlKQpjZWxsdHlwZV9hZHRfZGYKYGBgCgoKYGBge3J9CiMgUk5BIFVNQVA6CmFsbF9jZWxsdHlwZXNfcm5hX3VtYXAgPC0gY2VsbHR5cGVfYWR0X2RmICU+JQogIGRwbHlyOjpzZWxlY3QoVU1BUDEsIFVNQVAyLCBjZWxsdHlwZSkgJT4lCiAgZHBseXI6OmRpc3RpbmN0KCkgJT4lCiAgZ2dwbG90KCkgKwogIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjZWxsdHlwZSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAwLjMsIGFscGhhID0gMC40KSArIAogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MykpKSArCiAgZ2d0aXRsZShnbHVlOjpnbHVlKCJJbnRlZ3JhdGVkIFVNQVAgd2l0aCBhbGwgU2luZ2xlUi1hbm5vdGF0ZWQgY2VsbHR5cGVzIHNob3duIikpCgphbGxfY2VsbHR5cGVzX3JuYV91bWFwCmBgYApgYGB7cn0KYWxsX2NlbGx0eXBlc19ybmFfdW1hcCArCiAgbGltcygKICAgIHggPSBjKC02LCA0KSwgCiAgICB5ID0gYygtNSwgNS41KQogICkgKwogIGdndGl0bGUoIlRoZSBhYm92ZSBVTUFQLCB6b29tZWQgaW4iKQpgYGAKCgpgYGB7cn0KIyBSTkEgVU1BUDoKbWF4X2NlbGx0eXBlc19ybmFfdW1hcCA8LSBjZWxsdHlwZV9hZHRfZGYgJT4lCiAgZHBseXI6OnNlbGVjdChVTUFQMSwgVU1BUDIsIGNlbGx0eXBlX2NvbGxhcHNlZCkgJT4lCiAgZHBseXI6OmRpc3RpbmN0KCkgJT4lCiAgZ2dwbG90KCkgKwogIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjZWxsdHlwZV9jb2xsYXBzZWQpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMC4zLCBhbHBoYSA9IDAuNCkgKyAKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTMpKSkgKwogIGxhYnMoCiAgICB0aXRsZSA9IGdsdWU6OmdsdWUoIkludGVncmF0ZWQgVU1BUCB3aXRoIHRvcCB7cGFyYW1zJG1heF9jZWxsdHlwZXN9IFNpbmdsZVItYW5ub3RhdGVkIGNlbGx0eXBlcyBzaG93biIpLCAKICAgIGNvbG9yID0gZ2x1ZTo6Z2x1ZSgiVG9wIHtwYXJhbXMkbWF4X2NlbGx0eXBlc30gY2VsbHR5cGVzIikpCgptYXhfY2VsbHR5cGVzX3JuYV91bWFwCmBgYAoKYGBge3J9Cm1heF9jZWxsdHlwZXNfcm5hX3VtYXAgKwogIGxpbXMoCiAgICB4ID0gYygtNiwgNCksIAogICAgeSA9IGMoLTUsIDUuNSkKICApICsKICBnZ3RpdGxlKCJUaGUgYWJvdmUgVU1BUCwgem9vbWVkIGluIikKYGBgCgoKYGBge3J9CiMgZnVuY3Rpb24gdG8gbWFrZSBVTUFQcyBjb2xvcmVkIGJ5IEFEVAptYWtlX2FkdF91bWFwIDwtIGZ1bmN0aW9uKGNlbGx0eXBlX2FkdF9kZiwgYWR0X25hbWUpIHsKCiAgY2VsbHR5cGVfYWR0X2RmICU+JQogICAgZHBseXI6OmZpbHRlcihBRFQgPT0gYWR0X25hbWUpICU+JQogICAgZ2dwbG90KCkgKyAKICAgIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBsb2djb3VudHMpICsgCiAgICBnZW9tX3BvaW50KHNpemUgPSAwLjMsIGFscGhhID0gMC40KSArCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArIAogICAgbGFicygKICAgICAgdGl0bGUgPSBnbHVlOjpnbHVlKCJ7YWR0X25hbWV9IGV4cHJlc3Npb24iKSwKICAgICAgY29sb3IgPSAiTm9ybWFsaXplZCBleHByZXNzaW9uIikKfQoKbWFrZV9hZHRfdW1hcChjZWxsdHlwZV9hZHRfZGYsICJDRDMiKSAjIGJvdHRvbSBsZWZ0IGNvcm5lciB0aW55IGRvdHMhCm1ha2VfYWR0X3VtYXAoY2VsbHR5cGVfYWR0X2RmLCAiQ0QxMjMiKSAjIGV2ZXJ5dGhpbmcgaXMgdHVtb3IKbWFrZV9hZHRfdW1hcChjZWxsdHlwZV9hZHRfZGYsICJDRDQ1IikgIyBzaG91bGQgYmUgb24gYWxsIGRpZmZlcmVudGlhdGVkIGNlbGxzIG1vcmUgb3IgbGVzc1wKbWFrZV9hZHRfdW1hcChjZWxsdHlwZV9hZHRfZGYsICJDRDE5IikgIyBiIGNlbGxzCgpgYGAKCiMjIEFEVCBkaXN0cmlidXRpb25zCgpUaGlzIHNlY3Rpb24gZXhwbG9yZXMgQURUIGNvdW50cyBhY3Jvc3MgcHJlZGljdGVkIGNlbGx0eXBlcy4KCgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02fQoKYWR0X2NlbGx0eXBlX3Zpb2xpbiA8LSBmdW5jdGlvbihkZiwgY2VsbHR5cGVfY29sdW1uLCBhZHRfbmFtZSkgewogIAogICMgY29sb3JzLCB1c2luZyBkcGx5ciBzaW5jZSB0aWR5ZXZhbCBsYW5kCiAgZmlsbF92ZWN0b3IgPC0gZGYgJT4lCiAgICBkcGx5cjo6cHVsbCh7e2NlbGx0eXBlX2NvbHVtbn19KSAlPiUKICAgIHVuaXF1ZSgpICU+JQogICAgbGVuZ3RoKCkgJT4lCiAgICByYWluYm93KCkKCiAgY2VsbHR5cGVfYWR0X2RmICU+JQogICAgZHBseXI6OmZpbHRlcihBRFQgPT0gYWR0X25hbWUpICU+JQogICAgZHBseXI6Om11dGF0ZShjZWxsdHlwZV9wbG90X2NvbHVtbiA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKHt7Y2VsbHR5cGVfY29sdW1ufX0sIGxvZ2NvdW50cykpICU+JQogICAgZ2dwbG90KCkgKyAKICAgIGFlcyh4ID0gY2VsbHR5cGVfcGxvdF9jb2x1bW4sCiAgICAgICAgeSA9IGxvZ2NvdW50cywgCiAgICAgICAgZmlsbCA9IGNlbGx0eXBlX3Bsb3RfY29sdW1uKSArIAogICAgZ2VvbV92aW9saW4oYWxwaGEgPSAwLjcpICsgCiAgICBzdGF0X3N1bW1hcnkoKSArIAogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZmlsbF92ZWN0b3IsIAogICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJDZWxsdHlwZXMiKSArCiAgICBnZ3RpdGxlKGdsdWU6OmdsdWUoInthZHRfbmFtZX0gbm9ybWFsaXplZCBleHByZXNzaW9uIGFjcm9zcyBjZWxsdHlwZXMiKSkKCn0KCiMjIENEMTkKYWR0X2NlbGx0eXBlX3Zpb2xpbihjZWxsdHlwZV9hZHRfZGYsIGNlbGx0eXBlLCAiQ0QxOSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAzMCwgaGp1c3QgPSAxKSkKYWR0X2NlbGx0eXBlX3Zpb2xpbihjZWxsdHlwZV9hZHRfZGYsIGNlbGx0eXBlX2NvbGxhcHNlZCwgIkNEMTkiKQoKIyMgQ0QxMjMKYWR0X2NlbGx0eXBlX3Zpb2xpbihjZWxsdHlwZV9hZHRfZGYsIGNlbGx0eXBlLCAiQ0QxMjMiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzAsIGhqdXN0ID0gMSkpCmFkdF9jZWxsdHlwZV92aW9saW4oY2VsbHR5cGVfYWR0X2RmLCBjZWxsdHlwZV9jb2xsYXBzZWQsICJDRDEyMyIpCmBgYAoKCiMjIENvbXBhcmluZyByZWZlcmVuY2VzCgpUbyBidWlsZCBhIHNlbnNlIG9mIGhvdyBkaWZmZXJlbnQgcmVmZXJlbmNlcyBwZXJmb3JtLCBsZXQncyBqdXN0IGxvb2sgYXQgb25lIFNDRSAoYXJiaXRyYXJpbHkgY2hvc2VuIGFzIHRoZSBmaXJzdCk6CgpgYGB7cn0Kc2NlIDwtIHNjZV9saXN0W1sxXV0KYGBgCgoKRnVuY3Rpb25zIGZvciBjb21wYXJpbmcgcmVmZXJlbmNlczoKYGBge3J9CiMgUmV0dXJuIGEgdGliYmxlIG9mIHByZWRpY3Rpb25zIGZyb20gdGhlIHR3byByZWZlcmVuY2VzCgojIERlZmluZSBvdXRzaWRlIG9mIGZ1bmN0aW9uIHRvIGF2b2lkIHJlcnVubmluZyBvdmVyIGFuZCBvdmVyIGFuZCBvdmVyIEFORCBPVkVSLgpocGNhX3JlZiA8LSBjZWxsZGV4OjpIdW1hblByaW1hcnlDZWxsQXRsYXNEYXRhKGVuc2VtYmwgPSBUUlVFKQpibHVlcHJpbnRfZW5jb2RlX3JlZiA8LSBjZWxsZGV4OjpCbHVlcHJpbnRFbmNvZGVEYXRhKGVuc2VtYmwgPSBUUlVFKQoKY29tcGFyZV9wcmVkaWN0aW9ucyA8LSBmdW5jdGlvbihzY2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsX25hbWUpIHsKICAKICAjIEhQQ0EgcHJlZGljdGlvbgogIHByZWRzX2hwY2EgPC0gU2luZ2xlUjo6U2luZ2xlUigKICAgIHRlc3QgPSBzY2UsIAogICAgcmVmID0gaHBjYV9yZWYsCiAgICBsYWJlbHMgPSBjb2xEYXRhKGhwY2FfcmVmKVssbGFiZWxfbmFtZV0sIAogICAgQlBQQVJBTSA9IEJpb2NQYXJhbGxlbDo6TXVsdGljb3JlUGFyYW0oNCkKICApCiAgCiAgIyBCbHVlcHJpbnQvRU5DT0RFIHByZWRpY3Rpb24KICBwcmVkc19iZSA8LSBTaW5nbGVSOjpTaW5nbGVSKAogICAgdGVzdCA9IHNjZSwgCiAgICByZWYgPSBibHVlcHJpbnRfZW5jb2RlX3JlZiwKICAgIGxhYmVscyA9IGNvbERhdGEoYmx1ZXByaW50X2VuY29kZV9yZWYpWyxsYWJlbF9uYW1lXSwgCiAgICBCUFBBUkFNID0gQmlvY1BhcmFsbGVsOjpNdWx0aWNvcmVQYXJhbSg0KQogICkKICAKICAjIENvbWJpbmUgcmVzdWx0cyBpbnRvIGEgd2lkZSB0aWJibGU6CiAgcHJlZHNfZGYgPC0gdGliYmxlOjp0aWJibGUoCiAgICBocGNhID0gcHJlZHNfaHBjYSRsYWJlbHMsIAogICAgY2VsbF9iYXJjb2RlID0gcm93bmFtZXMocHJlZHNfaHBjYSkpICU+JQogICAgZHBseXI6OmlubmVyX2pvaW4oCiAgICAgIHRpYmJsZTo6dGliYmxlKAogICAgICAgIGJsdWVwcmludF9lbmNvZGUgPSBwcmVkc19iZSRsYWJlbHMsIAogICAgICAgIGNlbGxfYmFyY29kZSA9IHJvd25hbWVzKHByZWRzX2JlKSkgCiAgICApCgogIHJldHVybihwcmVkc19kZikKfQpgYGAKClRoZSBbYGNlbGxkZXhgXSgoaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvZGF0YS9leHBlcmltZW50L3ZpZ25ldHRlcy9jZWxsZGV4L2luc3QvZG9jL3VzZXJndWlkZS5odG1sKSkgcGFja2FnZSBjb250YWlucyBidWxrIFJOQS1TZXEgZGF0YXNldHMgZm9yIHVzZSBhcyByZWZlcmVuY2UuClRoZXNlIHR3byBhcmUgbGlrZWx5IG1vc3QgdXNlZnVsOgoKPiBUaGUgSFBDQSByZWZlcmVuY2UgY29uc2lzdHMgb2YgcHVibGljbHkgYXZhaWxhYmxlIG1pY3JvYXJyYXkgZGF0YXNldHMgZGVyaXZlZCBmcm9tIGh1bWFuIHByaW1hcnkgY2VsbHMgKE1hYmJvdHQgZXQgYWwuIDIwMTMpLiBNb3N0IG9mIHRoZSBsYWJlbHMgcmVmZXIgdG8gYmxvb2Qgc3VicG9wdWxhdGlvbnMgYnV0IGNlbGwgdHlwZXMgZnJvbSBvdGhlciB0aXNzdWVzIGFyZSBhbHNvIGF2YWlsYWJsZS4KCgo+IFRoZSBCbHVlcHJpbnQvRU5DT0RFIHJlZmVyZW5jZSBjb25zaXN0cyBvZiBidWxrIFJOQS1zZXEgZGF0YSBmb3IgcHVyZSBzdHJvbWEgYW5kIGltbXVuZSBjZWxscyBnZW5lcmF0ZWQgYnkgQmx1ZXByaW50IChNYXJ0ZW5zIGFuZCBTdHVubmVuYmVyZyAyMDEzKSBhbmQgRU5DT0RFIHByb2plY3RzIChUaGUgRU5DT0RFIFByb2plY3QgQ29uc29ydGl1bSAyMDEyKS4KCgoKYGBge3J9Cm1haW5fbGFiZWxzX2RmIDwtIGNvbXBhcmVfcHJlZGljdGlvbnMoc2NlLCAibGFiZWwubWFpbiIpCmZpbmVfbGFiZWxzX2RmIDwtIGNvbXBhcmVfcHJlZGljdGlvbnMoc2NlLCAibGFiZWwuZmluZSIpCgpgYGAKCkhvdyBkbyB0aGVzZSBwcmVkaWN0aW9ucyBjb21wYXJlPwpOb3RlIHRoYXQgdGhlc2UgcmVmZXJlbmNlcyBvZnRlbiB1c2UgZGlmZmVyZW50IGxhYmVscyBmb3IgdGhlIHNhbWUgY2VsbCB0eXBlIHNvIHdlIHNob3VsZG4ndCBleHBlY3QgYSBwZXJmZWN0IGRpYWdvbmFsIGJlbG93OgoKYGBge3J9CiNmdW5jdGlvbiBmb3IgbWFraW5nIGNvbXBhcmlzb24gaGVhdG1hcHMKbWFrZV9jb21wYXJpc29uX2hlYXRtYXAgPC0gZnVuY3Rpb24oZGYpIHsKICBkZiAlPiUKICAgIGRwbHlyOjptdXRhdGUoCiAgICAgIGhwY2EgPSBmb3JjYXRzOjpmY3RfcmV2KGZvcmNhdHM6OmZjdF9pbmZyZXEoaHBjYSkpLAogICAgICBibHVlcHJpbnRfZW5jb2RlID0gZm9yY2F0czo6ZmN0X3Jldihmb3JjYXRzOjpmY3RfaW5mcmVxKGJsdWVwcmludF9lbmNvZGUpKQogICAgKSAlPiUKICAgIGRwbHlyOjpjb3VudChocGNhLCBibHVlcHJpbnRfZW5jb2RlKSAlPiUKICBnZ3Bsb3QoKSArIAogIGFlcyh4ID0gYmx1ZXByaW50X2VuY29kZSwgeSA9IGhwY2EsIGZpbGwgPSBuKSArIAogIGdlb21fdGlsZShjb2xvciA9ICJibGFjayIpICsgCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4pKSArIAogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiUmRZbEJ1IiwgbmFtZSA9ICJOdW0uIGNlbGxzIikgKyAKICAjIGJhY2tncm91bmQgZ3JpZCBmcm9tIHRoZW1lX2J3IGRvZXMgbm90IG1hdGNoIHVwIHdpdGggcmVjdGFuZ2xlcy4KICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzAsIGhqdXN0ID0gMSkpCn0KYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD01fQptYWtlX2NvbXBhcmlzb25faGVhdG1hcChtYWluX2xhYmVsc19kZikgKyBnZ3RpdGxlKCJCcm9hZCBsYWJlbCBjb21wYXJpc29uIikKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQptYWtlX2NvbXBhcmlzb25faGVhdG1hcChmaW5lX2xhYmVsc19kZikgKyBnZ3RpdGxlKCJGaW5lIGxhYmVsIGNvbXBhcmlzb24iKQpgYGAKCgoKCiMjIFNlc3Npb24gSW5mbwoKYGBge3Igc2Vzc2lvbl9pbmZvfQpzZXNzaW9uSW5mbygpCmBgYAo=